-- LiteStats 0.9 / Katae @ WoWI
-------------------------------------------------------------------------------------------
-- * Configure the modules in config.lua, if you need more control, edit the core modules.
-- * AutoSell and AutoRepair can be toggled on/off by right-clicking the text display.
-- * Junk exceptions for AutoSelling can be configured by the /junk command.
-------------------------------------------------------------------------------------------
-- Individual scripts may freely be distributed with credit and permission.

local modules, profiles, font, t_icon, hints, layout = LPSTAT_CONFIG, LPSTAT_PROFILES, LPSTAT_FONT, LTIPICONSIZE or 20, TIPHINTS, {}
local realm, toon, class = GetRealmName(), UnitName'player', select(2, UnitClass'player')

local char = format("%s - %s", toon, realm)

-- Tooltip text colors
local tthead = {r=0.40, g=0.78, b=1.00} -- Headers
local ttsubh = {r=0.75, g=0.90, b=1.00} -- Subheaders

-- Strata/Level for text objects
local strata, level = "MEDIUM", 99

local format = string.format
local strmatch = string.match
local strfind = string.find
local strtrim = strtrim
local unpack = unpack
local pairs = pairs
local ipairs = ipairs
local floor = math.floor
local select = select
local max = max
local IsAltKeyDown = IsAltKeyDown

------------------------------------------
-- Saved Vars Init / Coords
local ls, coordX, coordY, Coords = CreateFrame'frame', 0, 0
ls:RegisterEvent'ADDON_LOADED'
ls:RegisterEvent'VARIABLES_LOADED'
ls:SetScript("OnEvent", function(_,event,addon)
	if event == "ADDON_LOADED" and addon == "LiteStats" and not modules then
		-- Missing config, print an error message.
		print("|cffFFFFFFLite|cff44CCFFStats|cffFFFFFF: Copy |cffFF9912config_default.lua|r to |cffFF9912config.lua|r and restart.")
	end
	if event == "VARIABLES_LOADED" then
		LPSTAT = LPSTAT or {}
		LPSTAT[char] = LPSTAT[char] or {}
		-- true/false defaults for autosell and autorepair
		if LPSTAT[char].AutoSell == nil then LPSTAT[char].AutoSell = true end
		if LPSTAT[char].AutoRepair == nil then LPSTAT[char].AutoRepair = false end
	end
	if event == "ZONE_CHANGED_NEW_AREA" and not WorldMapFrame:IsShown() then
		SetMapToCurrentZone()
	end
end)
if modules and ((modules.Coords and modules.Coords.enabled) or (modules.Location and modules.Location.enabled)) then
	ls:RegisterEvent'ZONE_CHANGED_NEW_AREA'
	ls:SetScript("OnUpdate", function()
		coordX, coordY = GetPlayerMapPosition'player'
	end)
	WorldMapFrame:HookScript("OnHide", SetMapToCurrentZone)
	function Coords()
		return format(modules.Coords and modules.Coords.fmt or "%d,%d", coordX*100, coordY*100)
	end
end

-- Set profile
if profiles then for _,p in ipairs{class,char} do
	if profiles[p] then for k,v in pairs(profiles[p]) do
		for _k,_v in pairs(v) do modules[k][_k] = _v end
	end end
end profiles = nil end

-- Config missing?
if not modules then return end

------------------------------------------
local function formatgold(style, amount)
	local gold, silver, copper = floor(amount * 0.0001), floor(mod(amount * 0.01, 100)), floor(mod(amount, 100))
	if style == 1 then
		return (gold > 0 and gold.."|cffffd700g|r " or "")
			.. (silver > 0 and silver.."|cffc7c7cfs|r " or "")
			.. ((copper > 0 or (gold == 0 and silver == 0)) and copper.."|cffeda55fc|r" or "")
	elseif style == 2 or not style then	
		return format("%.1f|cffffd700g|r", amount * 0.0001)
	elseif style == 3 then
		return format("|cffffd700%s|r.|cffc7c7cf%s|r.|cffeda55f%s|r", gold, silver, copper)
	elseif style == 4 then -- not really finished... but usable
		return (gold > 0 and gold.."|TInterface\\MoneyFrame\\UI-GoldIcon:12|t" or "")
		 	.. (silver > 0 and silver.."|TInterface\\MoneyFrame\\UI-SilverIcon:12|t" or "")
		 	.. ((copper > 0 or (gold == 0 and silver == 0)) and copper.."|TInterface\\MoneyFrame\\UI-CopperIcon:12|t" or "") .. " "
	end
end

local function fmttime(sec,table)
	local d,h,m,s = ChatFrame_TimeBreakDown(floor(sec))
	local string = gsub(gsub(format(" %dd %dh %dm "..((d==0 and h==0) and "%ds" or ""),d,h,m,s)," 0[dhms]"," "),"%s+"," ")
	return strtrim(not table and string or gsub(gsub(gsub(gsub(string,"d",table.days),"h",table.hours),"m",table.minutes),"s",table.seconds))
end

local function HideTT(self) GameTooltip:Hide() self.hovered = false end

local function NewStat(name, stat)
	local m = modules[name]	
	for k,v in pairs{ -- retrieving config variables from LPSTAT_CONFIG
		name = name, anchor_frame = m.anchor_frame,
		anchor_to = m.anchor_to, anchor_from = m.anchor_from,
		x_off = m.x_off, y_off = m.y_off,
		height = m.height, width = m.width,
		strata = strata, level = level,
	} do if not stat[k] then stat[k] = v end end
	if not stat.text then stat.text = {} end
	-- retrieve font variables and insert them into text table
	for k,v in pairs(font) do if not stat.text[k] then stat.text[k] = v end end
	if stat.OnEnter then if stat.OnLeave then hooksecurefunc(stat,"OnLeave",HideTT) else stat.OnLeave = HideTT end end
	tinsert(layout, stat)
end

local function blank(module)
	-- A fix for anchoring to disabled modules.
	-- If text alignment is off by 1px, this is why.
	NewStat(module,{height=1,width=1})
	modules[module] = nil
end

local function AltUpdate(self)
	if not self.hovered then return end
	if IsAltKeyDown() and not self.altdown then self.altdown = true self:GetScript("OnEnter")(self)
	elseif not IsAltKeyDown() and self.altdown then self.altdown = false self:GetScript("OnEnter")(self) end
end

SLASH_LSTATS1, SLASH_LSTATS2 = "/lstats", "/litestats"
function SlashCmdList.LSTATS()
	local m,l = "|cffBCEE68", "|cffFF9912"
	print("|cffffffffLite|cff66C6FFStats|cffffffff tips:")
	if modules.Memory.enabled then
		print(m,"Memory")
		print(l," -|r Right-Click collects Lua garbage.")
	end if modules.Gold.enabled then
		print(m,"Gold")
		print(l," -|r Left-Click opens currency tab.")
		print(l," -|r Right-Click toggles AutoSelling.")
		print(l," -|r Watched currency tab items will reflect onto the tooltip.")
	end if modules.Durability.enabled then
		print(m,"Durability")
		print(l," -|r Left-Click opens character tab.")
		print(l," -|r Right-Click toggles AutoRepairing.")
	end if modules.Location.enabled or modules.Coords.enabled then
		print(m,"Location/Coords")
		print(l," -|r Clicking opens world map.")
		print(l," -|r Shift-Clicking location or coords module inserts your coords into chat.")
	end if modules.Clock.enabled then
		print(m,"Clock")
		print(l," -|r Left-Click opens calendar (/cal).")
		print(l," -|r Right-Click opens time manager frame.")
		print(l," -|r Local/realm & 24hr time can be toggled from the time manager.")
	end if modules.Friends.enabled or modules.Guild.enabled then
		print(m,"Friends/Guild")
		print(l," -|r Hold alt key to view ranks, notes and officer notes.")
		print(l," -|r (Guild) Right-Click to change the sorting, shift-right-click to reverse order.")
	end if modules.Talents.enabled then
		print(m,"Talents")
		print(l," -|r Left-Click opens the talent UI frame.")
		print(l," -|r Right-Click toggles your dual specs.")
	end if modules.Experience.enabled then
		print(m,"Experience/Time Played")
		print(l," -|r When below max level, a tooltip will be displayed with experience details.")
		print(l," -|r When at max level, the tooltip will show time played details for the account.")
		print(l," -|r If not at max level, holding alt will show the time played tooltip.")
	end
	print(m,"Other options may be configured in |cff66C6FFLiteStats\\config.lua")
end

------------------------------------------
--  FPS
if not modules.FPS.enabled then blank'FPS' else
	NewStat("FPS", {
		text = { string = function() return format(modules.FPS.fmt, floor(GetFramerate())) end }
	})
end
------------------------------------------
--  Latency
if not modules.Latency.enabled then blank'Latency' else
	NewStat("Latency", {
		text = { string = function() return format(modules.Latency.fmt, select(3,GetNetStats())) end }
	})
end
------------------------------------------
--	Mail
if not modules.Mail.enabled then blank'Mail' else
	MiniMapMailFrame.Show = MiniMapMailFrame.Hide
	NewStat("Mail", {
		text = { string = function() return modules.Mail[HasNewMail() and "newmail" or "nomail"] end },
		OnEnter = function(self)
			if HasNewMail() then
				GameTooltip:SetOwner(self, modules.Mail.tip_anchor, modules.Mail.tip_x, modules.Mail.tip_y)
				MinimapMailFrameUpdate()
				GameTooltip:Show()				
			end
		end
	})
end
------------------------------------------
-- Coordinates
if not modules.Coords.enabled then blank'Coords' else
	NewStat("Coords", {
		text = { string = Coords },
		OnClick = function(_,button)
			if button == "LeftButton" then
				ToggleFrame(WorldMapFrame)
			else
				ChatFrameEditBox:Show()
				ChatFrameEditBox:Insert(format(" (%s: %s)", GetZoneText(), Coords()))
			end
		end
	})
end
------------------------------------------
--  Memory
if not modules.Memory.enabled then blank'Memory' else
	local function sortdesc(a, b) return a[2] > b[2] end	
	local function formatmem(val,dec)
		return format("%."..(dec or 1).."f "..(val > 1024 and "MB" or "KB"), val / (val > 1024 and 1024 or 1))
	end
	local memory = {}
	NewStat("Memory", {
		text = {
			string = function(self)
				self.total = 0
				UpdateAddOnMemoryUsage()
				for i = 1, GetNumAddOns() do self.total = self.total + GetAddOnMemoryUsage(i) end
				if self.hover then self:GetParent():GetScript("OnEnter")(self:GetParent()) end
				return (self.total >= 1024 and format(modules.Memory.fmt_mb, self.total / 1024) or format(modules.Memory.fmt_kb, self.total))
			end, update = 5,
		},
		OnEnter = function(self)
			self.text.hover = true
			GameTooltip:SetOwner(self, modules.Memory.tip_anchor, modules.Memory.tip_x, modules.Memory.tip_y)
			GameTooltip:ClearLines()
			GameTooltip:AddDoubleLine("Memory Info", "Addons: "..formatmem(self.text.total),tthead.r,tthead.g,tthead.b,tthead.r,tthead.g,tthead.b)
			GameTooltip:AddLine' '
			if modules.Memory.max_addons ~= 0 then
				if not self.timer or self.timer + 5 < time() then
					self.timer = time()
					UpdateAddOnMemoryUsage()
					for i = 1, #memory do memory[i] = nil end
					for i = 1, GetNumAddOns() do
						local addon, name = GetAddOnInfo(i)
						if IsAddOnLoaded(i) then tinsert(memory,{name or addon, GetAddOnMemoryUsage(i)}) end
					end
					table.sort(memory, sortdesc)				
				end
				local exmem = 0
				for i,t in ipairs(memory) do
					if modules.Memory.max_addons and i > modules.Memory.max_addons then
						exmem = exmem + t[2]
					else
						local r,g
						if t[2] <= 102.4 then r,g = 0,1 -- 0-99
						elseif t[2] <= 512 then r,g = 0.75,1 -- 100 - 511
						elseif t[2] <= 1024 then r,g = 1,1 -- 512 - 1mb
						elseif t[2] <= 2560 then r,g = 1,0.75 -- 1mb - 2.5mb
						elseif t[2] <= 5120 then r,g = 1,0.5 -- 2.5mb - 5mb
						else r,g = 1,0.1 end -- 5mb +
						GameTooltip:AddDoubleLine(t[1],formatmem(t[2]),1,1,1,r,g,0)
					end
				end
				if exmem > 0 then
					local more = #memory - modules.Memory.max_addons
					GameTooltip:AddDoubleLine(format("+ %d More...", more),formatmem(exmem),ttsubh.r,ttsubh.g,ttsubh.b,ttsubh.r,ttsubh.g,ttsubh.b)
				end
				GameTooltip:AddDoubleLine(" ","--------------",1,1,1,0.5,0.5,0.5)
			end
			GameTooltip:AddDoubleLine("Default UI Memory Usage:", formatmem(gcinfo() - self.text.total),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
			GameTooltip:AddDoubleLine("Total Memory Usage:", formatmem(collectgarbage'count'),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
	        GameTooltip:Show()
		end,
		OnLeave = function(self) self.text.hover = false end,
		OnClick = function(self, button)
			if button == "RightButton" then 
				UpdateAddOnMemoryUsage()
				local before = gcinfo()
				collectgarbage()
				UpdateAddOnMemoryUsage()
				print("|cff66C6FFGarbage collected:|r "..formatmem(before - gcinfo()))
				self.timer, self.text.elapsed = nil, 5
				self:GetScript("OnEnter")(self)
			end
		end
	})
end
------------------------------------------
--  Durability
if not modules.Durability.enabled then blank'Durability' else
	NewStat("Durability", {
		OnLoad = function(self)
			CreateFrame("GameTooltip", "LPDURA")
			LPDURA:SetOwner(WorldFrame,"ANCHOR_NONE")
			if modules.Durability.man then DurabilityFrame.Show = DurabilityFrame.Hide end
			self:RegisterEvent'UPDATE_INVENTORY_DURABILITY'
			self:RegisterEvent'PLAYER_LOGIN'
			self:RegisterEvent'MERCHANT_SHOW'
		end,
		OnEvent = function(self, event, ...)
			if event == "UPDATE_INVENTORY_DURABILITY" or event == "PLAYER_LOGIN" then
				local durability = 100
				for id = 1, 18 do
			        local dur, dmax = GetInventoryItemDurability(id)
					if dur ~= dmax then durability = floor(min(durability, dur / dmax * 100)) end
				end
				self.text:SetText(format(modules.Durability.fmt, durability))
			elseif event == "MERCHANT_SHOW" and not IsAltKeyDown() then
				if LPSTAT[char].AutoRepair and CanMerchantRepair() then
			        local cost, total = GetRepairAllCost(), 0
			        if cost > 0 then
						if modules.Durability.gfunds and CanGuildBankRepair() then RepairAllItems(1); total = cost end
						if GetRepairAllCost() > 0 then
							if not modules.Durability.ignore_inventory and GetRepairAllCost() <= GetMoney() then
								total = GetRepairAllCost(); RepairAllItems()
							else
								for id = 1, 18 do
									local cost = select(3,LPDURA:SetInventoryItem("player",id))
									if cost ~= 0 and cost <= GetMoney() then
										if not InRepairMode() then ShowRepairCursor() end
										PickupInventoryItem(id)
										total = total + cost
									end
								end
							end
							HideRepairCursor()
						end
						if total > 0 then print(format("|cff66C6FF%s |cffFFFFFF%s",REPAIR_COST,formatgold(1, total))) end
					end
			    end
			end
		end,
		OnEnter = function(self)
			GameTooltip:SetOwner(self, modules.Durability.tip_anchor, modules.Durability.tip_x, modules.Durability.tip_y)
			GameTooltip:ClearLines()
			GameTooltip:AddLine(DURABILITY,tthead.r,tthead.g,tthead.b)
			GameTooltip:AddLine' '
			local nodur, totalcost = true, 0
			for slot, string in ("1HEAD3SHOULDER5CHEST6WAIST7LEGS8FEET9WRIST10HANDS16MAINHAND17SECONDARYHAND18RANGED"):gmatch("(%d+)([^%d]+)") do
				local dur, dmax = GetInventoryItemDurability(slot)
				local string = _G[string.."SLOT"]
				if dur ~= dmax then
					local perc = dur ~= 0 and dur/dmax or 0					
					local seg, relperc = math.modf(perc*2) local r1,g1,b1,r2,g2,b2 = select(seg*3+1,1,0,0,1,1,0,0,1,0)
					local hex = format("|cff%02x%02x%02x",(r1+((r2 or 0)-r1)*relperc)*255,(g1+((g2 or 0)-g1)*relperc)*255,(b1+((b2 or 0)-b1)*relperc)*255)
					GameTooltip:AddDoubleLine(modules.Durability.gear_icons and format("|T%s:%d|t %s",GetInventoryItemTexture('player',slot),t_icon,string) or string,format("|cffaaaaaa%s/%s | %s%s%%",dur,dmax,hex,floor(perc*100)),1,1,1)
					totalcost, nodur = totalcost + select(3,LPDURA:SetInventoryItem("player",slot))
				end
			end
			if nodur then
				GameTooltip:AddLine("Good as new!",1,1,1)
			else
				GameTooltip:AddDoubleLine(" ","--------------",1,1,1,0.5,0.5,0.5)
				GameTooltip:AddDoubleLine(REPAIR_COST, formatgold(1, totalcost),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)	
			end
			GameTooltip:AddLine' '
			GameTooltip:AddDoubleLine(" ","AutoRepair: "..(LPSTAT[char].AutoRepair and "|cff55ff55ON" or "|cffff5555OFF"),1,1,1,ttsubh.r,ttsubh.g,ttsubh.b)
			GameTooltip:Show()
		end,
		OnClick = function(self, button)
			if button == "RightButton" then
				LPSTAT[char].AutoRepair = not LPSTAT[char].AutoRepair
				self:GetScript("OnEnter")(self)
			elseif button == "LeftButton" then
				ToggleCharacter'PaperDollFrame'
			end
		end
	})
end
------------------------------------------
--  Gold
if not modules.Gold.enabled then blank'Gold' else
	NewStat("Gold", {
		OnLoad = function(self)
			self.started = GetMoney()
			self:RegisterEvent'PLAYER_LOGIN'
			self:RegisterEvent'PLAYER_MONEY'
			self:RegisterEvent'MERCHANT_SHOW'
			if not LPSTAT.JunkIgnore then LPSTAT.JunkIgnore = {} end
		end,
		OnEvent = function(self, event)
			LPSTAT[char].Gold = GetMoney()
			if event == "MERCHANT_SHOW" then
				if LPSTAT[char].AutoSell and not IsAltKeyDown() then
					local profit = 0
					for bag = 0, NUM_BAG_SLOTS do for slot = 0, GetContainerNumSlots(bag) do
						local link = GetContainerItemLink(bag, slot)
						if link then
							local itemstring, ignore = strmatch(link,"|Hitem:(%d-):"), false
							for _, exception in pairs(LPSTAT.JunkIgnore) do
								if exception == itemstring then ignore = true; break end
							end
							if select(3, GetItemInfo(link)) == 0 and not ignore then
								profit = profit + select(11, GetItemInfo(link)) * select(2, GetContainerItemInfo(bag, slot))
								UseContainerItem(bag, slot)
							end
						end
					end end
					if profit > 0 then print(format("|cff66C6FFJunk profit: |cffFFFFFF%s", formatgold(1, profit))) end
				end
				return
			end
			self.text:SetText(formatgold(modules.Gold.style, LPSTAT[char].Gold))
		end,
		OnEnter = function(self)
			local curgold = GetMoney()
			LPSTAT[char].Gold = curgold
			GameTooltip:SetOwner(self, modules.Gold.tip_anchor, modules.Gold.tip_x, modules.Gold.tip_y)
			GameTooltip:ClearLines()
			GameTooltip:AddLine(CURRENCY,tthead.r,tthead.g,tthead.b)
			GameTooltip:AddLine' '
			if self.started ~= curgold then
				local gained = curgold > self.started
				local color = gained and "|cff55ff55" or "|cffff5555"
				GameTooltip:AddDoubleLine("Session Gain/Loss", format("%s$|r %s %s$|r",color,formatgold(1, abs(self.started - curgold)),color),1,1,1,1,1,1)
				GameTooltip:AddLine' '
			end
			GameTooltip:AddLine("Server Gold",ttsubh.r,ttsubh.g,ttsubh.b)
			local total = 0
			for char, conf in pairs(LPSTAT) do
				local name, realm = strmatch(char, '(.+) %- (.+)')
				if realm == GetRealmName() and conf.Gold and conf.Gold > 99 then
					GameTooltip:AddDoubleLine(name, formatgold(1, conf.Gold),1,1,1,1,1,1)
					total = total + conf.Gold
				end
			end
			GameTooltip:AddDoubleLine(" ","-----------------",1,1,1,0.5,0.5,0.5)
			GameTooltip:AddDoubleLine("Total", formatgold(1, total),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
			GameTooltip:AddLine' '
			
			local currencies = 0
			for i = 1, GetCurrencyListSize() do
				local name,_,_,_,watched,count,ctype,icon = GetCurrencyListInfo(i)
				if watched then
					if currencies == 0 then GameTooltip:AddLine(format("%s %s",PLAYER,CURRENCY),ttsubh.r,ttsubh.g,ttsubh.b) end
					if ctype == 1 then
						icon = "Interface\\PVPFrame\\PVP-ArenaPoints-Icon"
					elseif ctype == 2 then
						icon = format("Interface\\AddOns\\LiteStats\\%sIcon",UnitFactionGroup'player')
					end
					local r,g,b
					if count > 0 then r,g,b = 1,1,1 else r,g,b = 0.5,0.5,0.5 end
					GameTooltip:AddDoubleLine(name, format("%d |T%s:%d|t",count,icon,t_icon),r,g,b,r,g,b)
					currencies = currencies + 1
				end
			end
			if currencies > 0 then GameTooltip:AddLine' ' end
			GameTooltip:AddDoubleLine(" ","AutoSell grays: "..(LPSTAT[char].AutoSell and "|cff55ff55ON" or "|cffff5555OFF"),1,1,1,ttsubh.r,ttsubh.g,ttsubh.b)
			GameTooltip:Show()
		end,
		OnClick = function(self, button)
			if button == "LeftButton" then
				ToggleCharacter'TokenFrame'
			elseif button == "RightButton" then
				LPSTAT[char].AutoSell = not LPSTAT[char].AutoSell				
				self:GetScript("OnEnter")(self)				
			end
		end
	})
	SLASH_KJUNK1 = "/junk"
	function SlashCmdList.KJUNK(s) 
		local action = strsplit(" ",s)
		if action == "list" then
			print("|cff66C6FFJunk exceptions:|r",(#LPSTAT.JunkIgnore == 0 and NONE or ""))
			for i, id in pairs(LPSTAT.JunkIgnore) do
				local link = select(2, GetItemInfo(id))
				print("- ["..i.."]",link)
			end
		elseif action == "clear" then
			LPSTAT.JunkIgnore = {}
			print("|cff66C6FFCleared junk exceptions list.")
		elseif action == "add" or strfind(action,"^del") or strfind(action,"^rem") then
			for id in s:gmatch("|Hitem:(%d-):") do
				local link = select(2, GetItemInfo(id))
				if action == "add" then
					if select(3, GetItemInfo(id)) == 0 then
						if not tContains(LPSTAT.JunkIgnore,id) then
							tinsert(LPSTAT.JunkIgnore,id)
							print('|cff66C6FFAdded junk exception:|r',link)
						else
							print(link,'|cff66C6FFis already in exceptions list.')
						end
					else print(link,'|cff66C6FFis not junk.') end
				elseif strfind(action,"^del") or strfind(action,"^rem") then
					tDeleteItem(LPSTAT.JunkIgnore,id)
					print('|cff66C6FFRemoved junk exception:|r',link)
				end
			end
		else
			print('|cffffffffLite|cff66C6FFStats|r: Junk List')
			print('/junk <add||rem(ove)> [itemlink] - Add/remove exception.')
			print('/junk list - List currently ignored items.')
			print('/junk clear - Clear exceptions list.')
		end
	end
end
------------------------------------------
--  Clock
if not modules.Clock.enabled then blank'Clock' else
	GameTimeFrame:Hide()
	NewStat("Clock", { -- height = 11,
		text = {
			string = function()
				return gsub(gsub(gsub(GameTime_GetTime(true),'%s*AM',modules.Clock.AM),'%s*PM',modules.Clock.PM),':',modules.Clock.colon)
			end
		},
		OnLoad = function(self) RequestRaidInfo() self:RegisterEvent'UPDATE_INSTANCE_INFO' end,
		OnEvent = function(self) if self.hovered then self:GetScript("OnEnter")(self) end end,
		OnEnter = function(self)
			if not self.hovered then RequestRaidInfo() end
			self.hovered = true	
			GameTooltip:SetOwner(self, modules.Clock.tip_anchor, modules.Clock.tip_x, modules.Clock.tip_y)
			GameTooltip:ClearLines()
			GameTooltip:AddLine(date'%A, %B %d',tthead.r,tthead.g,tthead.b)
			GameTooltip:AddLine' '
			GameTooltip:AddDoubleLine(gsub(TIMEMANAGER_TOOLTIP_LOCALTIME,':',''),gsub(gsub(GameTime_GetLocalTime(true),'%s*AM','am'),'%s*PM','pm'),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
			GameTooltip:AddDoubleLine(gsub(TIMEMANAGER_TOOLTIP_REALMTIME,':',''),gsub(gsub(GameTime_GetGameTime(true),'%s*AM','am'),'%s*PM','pm'),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
			GameTooltip:AddLine' '
			GameTooltip:AddDoubleLine("Time to Wintergrasp",GetWintergraspWaitTime() and fmttime(GetWintergraspWaitTime()) or "In Progress",ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
			local oneraid
			for i = 1, GetNumSavedInstances() do
				local name,_,reset,difficulty,locked,extended,_,isRaid,maxPlayers = GetSavedInstanceInfo(i)
				if isRaid and (locked or extended) then
					local tr,tg,tb,diff
					if not oneraid then
						GameTooltip:AddLine' '
						GameTooltip:AddLine("Saved Raids",ttsubh.r,ttsubh.g,ttsubh.b)
						oneraid = true
					end
					if extended then tr,tg,tb = 0.3,1,0.3 else tr,tg,tb = 1,1,1 end
					if difficulty == 3 or difficulty == 4 then diff = "H" else diff = "N" end
					GameTooltip:AddDoubleLine(format("%s |cffaaaaaa(%s%s)",name,maxPlayers,diff),fmttime(reset),1,1,1,tr,tg,tb)
				end
			end
			GameTooltip:Show()
		end,
		OnClick = function(_,b) (b == "RightButton" and ToggleTimeManager or ToggleCalendar)() end
	})
end
------------------------------------------
--  Location
if not modules.Location.enabled then blank'Location' else
	NewStat("Location", { -- height = 13, width = 113, 
		OnLoad = function(self)
			MiniMapWorldMapButton:Hide()
			self:RegisterEvent'ZONE_CHANGED'
			self:RegisterEvent'ZONE_CHANGED_INDOORS'
			self:RegisterEvent'ZONE_CHANGED_NEW_AREA'
			self:RegisterEvent'PLAYER_ENTERING_WORLD'
			-- Territory names and colors.
			self.sanctuary = {SANCTUARY_TERRITORY, {0.41,0.8,0.94}}
			self.arena = {FREE_FOR_ALL_TERRITORY, {1,0.1,0.1}}
			self.friendly = {FACTION_CONTROLLED_TERRITORY, {0.1,1,0.1}}
			self.hostile = {FACTION_CONTROLLED_TERRITORY, {1,0.1,0.1}}
			self.contested = {CONTESTED_TERRITORY, {1,0.7,0}}
			self.combat = {COMBAT_ZONE, {1,0.1,0.1}}
			self.neutral = {format(FACTION_CONTROLLED_TERRITORY,FACTION_STANDING_LABEL4), {1,0.93,0.76}}
		end,
		OnEvent = function(self)
			self.subzone, self.zone, self.pvp = GetSubZoneText(), GetZoneText(), {GetZonePVPInfo()}
			if not self.pvp[1] then self.pvp[1] = "neutral" end
			local label = (self.subzone ~= "" and modules.Location.subzone) and self.subzone or self.zone
			local r,g,b = unpack(self.pvp[1] and (self[self.pvp[1]][2] or self.other) or self.other)
			self.text:SetText(modules.Location.truncate == 0 and label or strtrim(strsub(label,1,modules.Location.truncate)))
			self.text:SetTextColor(r,g,b,font.alpha)
		end,
		OnUpdate = function(self,u)
			if self.hovered then
				self.elapsed = self.elapsed + u
				if self.elapsed > 1 or self.init then
					GameTooltip:ClearLines()
					GameTooltip:AddLine(format("%s |cffffffff(%s)",self.zone,Coords()),tthead.r,tthead.g,tthead.b,1,1,1)
					if self.pvp[1] and not IsInInstance() then
						local r,g,b = unpack(self[self.pvp[1]][2])
						if self.subzone and self.subzone ~= self.zone then GameTooltip:AddLine(self.subzone,r,g,b) end				
						GameTooltip:AddLine(format(self[self.pvp[1]][1],self.pvp[3] or ""),r,g,b)
					end
					GameTooltip:Show()
					self.elapsed, self.init = 0, false
				end
			end
		end,
		OnEnter = function(self)
			self.hovered, self.init = true, true
			GameTooltip:SetOwner(self, modules.Location.tip_anchor, modules.Location.tip_x, modules.Location.tip_y)
		end,
		OnClick = function(self,button)
			if IsShiftKeyDown() then
				ChatFrameEditBox:Show()
				ChatFrameEditBox:Insert(format(" (%s: %s)",self.zone,Coords()))
			else ToggleFrame(WorldMapFrame) end
		end
	})
end
------------------------------------------
--  Tracking
if not modules.Tracking.enabled then blank'Tracking' else
	MiniMapTracking:Hide()
	NewStat("Tracking", { -- height = 11,
		OnLoad = function(self)
			self:RegisterEvent'PLAYER_LOGIN'
			self:RegisterEvent'MINIMAP_UPDATE_TRACKING'
		end,
		OnEvent = function(self)
			local track = "None"
			for id = 1, GetNumTrackingTypes() do
		        local name, _, active, category = GetTrackingInfo(id)
		        if active then track = gsub(gsub(gsub(name,"Profession","Prof."),"Low Level","Lowbie"),"Find Fish","Fishies!"); break end
		    end
			self.text:SetText(format(gsub(modules.Tracking.fmt,"%[icon%]",format("|T%s:%d|t",GetTrackingTexture(),modules.Tracking.iconsize)), track))
		end,
		OnClick = function(self) ToggleDropDownMenu(1,nil,MiniMapTrackingDropDown,self,0,5) end
	})
end
------------------------------------------
--  Ping
if not modules.Ping.enabled then blank'Ping' else
	NewStat("Ping", {
		text = { alpha = 1 },
		OnLoad = function(self) self:RegisterEvent'MINIMAP_PING' end,
		OnEvent = function(self, event, unit)
			if unit == "player" and modules.Ping.hide_self then return end
			if (unit == "player" and self.timer and time() - self.timer > 1) or not self.timer or unit ~= "player" then
				local class = (CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS)[select(2,UnitClass(unit))]
				self.text:SetText(format(modules.Ping.fmt, UnitName(unit)))
				self.text:SetTextColor(class.r,class.g,class.b,1)
	 			if UIFrameIsFading(self) then UIFrameFlashRemoveFrame(self) end
				UIFrameFlash(self,0.2,2.8,8,false,0,5)
				self.timer = time()
			end
		end
	})
end
------------------------------------------
--	DPS
if not modules.DPS.enabled then blank'DPS' else
	NewStat("DPS", {
		text = {
			string = function(self)
				self = self:GetParent()
				self.dps = self.active and self.damage / (GetTime() - self.starttime) or self.dps or 0
				return format(modules.DPS.fmt, self.dps)
			end
		},
		OnLoad = function(self)
			self:RegisterEvent'UNIT_PET'
			self:RegisterEvent'COMBAT_LOG_EVENT_UNFILTERED'
			self:RegisterEvent'PLAYER_REGEN_ENABLED'
			self:RegisterEvent'PLAYER_REGEN_DISABLED'
			self.events = "SWING_DAMAGE RANGE_DAMAGE SPELL_DAMAGE SPELL_PERIODIC_DAMAGE DAMAGE_SHIELD DAMAGE_SPLIT"
			self.player, self.pet, self.damage, self.active = UnitGUID'player', UnitGUID'pet' or '0x0', 0, false
			if modules.DPS.hide_inactive then self:Hide() end
		end,
		OnEvent = function(self, event, ...)
			if event == "COMBAT_LOG_EVENT_UNFILTERED" then
				local _,spellevent,unit,_,_,_,_,_,type,_,_,damage = ...
				if not strmatch(self.events,spellevent) then return end
				if unit == self.player or unit == self.pet then
					if self.damage == 0 then
						self.starttime, self.active = GetTime(), true 
						self.text:SetText(format(modules.DPS.fmt, 0))
					end
					if not self:IsShown() and modules.DPS.hide_inactive then self:Show() end
					if spellevent == "SWING_DAMAGE" then damage = type end
					self.damage = self.damage + damage
				end
			elseif event == "UNIT_PET" then
				self.pet = UnitGUID'pet' or '0x0'
			elseif event == "PLAYER_REGEN_ENABLED" then	
				if modules.DPS.hide_inactive then self:Hide() end
				self.active = false
			elseif event == "PLAYER_REGEN_DISABLED" then
				self.damage = 0
			end
		end,
		OnClick = function(self, button)
			if button == "RightButton" then
				self.starttime, self.damage, self.dps = GetTime(), 0, 0
				self.text:SetText(format(modules.DPS.fmt, self.dps))
			end
		end
	})
end
------------------------------------------
--	Guild
if not modules.Guild.enabled then blank'Guild' else
	hooksecurefunc("SortGuildRoster", function(type) CURRENT_GUILD_SORTING = type end)
	NewStat("Guild", {
		text = {
			string = function()
				if IsInGuild() then
					local online, total = 0, GetNumGuildMembers(true)
					for i = 0, total do if select(9, GetGuildRosterInfo(i)) then online = online + 1 end end
					return format(modules.Guild.fmt, online, total)
				else return "No Guild" end
			end, update = 5
		},
		OnLoad = function(self)
			GuildRoster()
			SortGuildRoster(modules.Guild.sorting == "note" and "rank" or "note")
			SortGuildRoster(modules.Guild.sorting)
			self:RegisterEvent'PARTY_MEMBERS_CHANGED'
		end,
		OnEvent = function(self) if self.hovered then self:GetScript("OnEnter")(self) end end,
		OnUpdate = function(self,u)
			if IsInGuild() then
				AltUpdate(self)
				if not self.gmotd then
					-- Give me the GMOTD *today* pls.
					if self.elapsed > 1 then GuildRoster(); self.elapsed = 0 end
				 	if GetGuildRosterMOTD() ~= '' then self.gmotd = true; if self.hovered then self:GetScript("OnEnter")(self) end end
					self.elapsed = self.elapsed + u
				end
			end
		end,
		OnClick = function(self,b)
			if b == "LeftButton" then
				ToggleFriendsFrame(3)
			elseif b == "RightButton" then
				local s = CURRENT_GUILD_SORTING
				SortGuildRoster(IsShiftKeyDown() and s or (IsAltKeyDown() and (s == "rank" and "note" or "rank") or s == "class" and "name" or s == "name" and "level" or s == "level" and "zone" or "class"))
				self:GetScript("OnEnter")(self)
			end
		end,
		OnEnter = function(self)
			if IsInGuild() then
				self.hovered = true
				GuildRoster()
				local name, rank, level, zone, note, officernote, connected, status, class, zone_r, zone_g, zone_b, classc, levelc, grouped
				local online, total, gmotd = 0, GetNumGuildMembers(true), GetGuildRosterMOTD()
				for i = 0, total do if select(9, GetGuildRosterInfo(i)) then online = online + 1 end end
				
				GameTooltip:SetOwner(self, modules.Guild.tip_anchor, modules.Guild.tip_x, modules.Guild.tip_y)
				GameTooltip:ClearLines()
				GameTooltip:AddDoubleLine(GetGuildInfo'player',format("%s: %d/%d",GUILD_ONLINE_LABEL,online,total),tthead.r,tthead.g,tthead.b,tthead.r,tthead.g,tthead.b)
				if gmotd ~= "" then GameTooltip:AddLine(format("  %s |cffaaaaaa- |cffffffff%s",GUILD_MOTD,gmotd),ttsubh.r,ttsubh.g,ttsubh.b,1) end
				if modules.Guild.maxguild ~= 0 and online > 1 then
					GameTooltip:AddLine' '
					for i = 1, total do
						if modules.Guild.maxguild and i > modules.Guild.maxguild then
							if online > 2 then GameTooltip:AddLine(format("+ %d More...", online - modules.Guild.maxguild),ttsubh.r,ttsubh.g,ttsubh.b) end
							break
						end
						-- name, rank, rankIndex, level, class, zone, note, officernote, online, status, classFileName
						name, rank, _, level, _, zone, note, officernote, connected, status, class = GetGuildRosterInfo(i)
						if connected and level >= modules.Guild.threshold and name ~= UnitName'player' then
							if GetRealZoneText() == zone then zone_r, zone_g, zone_b = 0.3, 1.0, 0.3 else zone_r, zone_g, zone_b = 0.65, 0.65, 0.65 end
							classc, levelc = (CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS)[class], GetQuestDifficultyColor(level)
							grouped = (UnitInParty(name) or UnitInRaid(name)) and (GetRealZoneText() == zone and " |cff7fff00*|r" or " |cffff7f00*|r") or ""
							if self.altdown then
								GameTooltip:AddDoubleLine(format("%s%s |cff999999- |cffffffff%s",grouped,name,rank),zone,classc.r,classc.g,classc.b,zone_r,zone_g,zone_b)
								if note ~= "" then GameTooltip:AddLine('  "'..note..'"',ttsubh.r,ttsubh.g,ttsubh.b,1) end
								if officernote ~= "" then GameTooltip:AddLine("  o: "..officernote,0.3,1,0.3,1) end
							else
								GameTooltip:AddDoubleLine(format("|cff%02x%02x%02x%d|r %s%s%s",levelc.r*255,levelc.g*255,levelc.b*255,level,name,(status~="" and " "..status or ""),grouped),zone,classc.r,classc.g,classc.b,zone_r,zone_g,zone_b)
							end
						end
					end
					GameTooltip:AddLine' '
					GameTooltip:AddDoubleLine(" ","Sorting by "..CURRENT_GUILD_SORTING,1,1,1,ttsubh.r,ttsubh.g,ttsubh.b)
				end
				GameTooltip:Show()
			end
		end
	})
end
------------------------------------------
--	Friends
if not modules.Friends.enabled then blank'Friends' else
	NewStat("Friends", {
		OnLoad = function(self)
			self:RegisterEvent'PLAYER_LOGIN'
			self:RegisterEvent'FRIENDLIST_UPDATE'
			self:RegisterEvent'PARTY_MEMBERS_CHANGED'
		end,
		OnEvent = function(self, event)
			if event ~= "PARTY_MEMBERS_CHANGED" then
				local online, total = 0, GetNumFriends()
				for i = 0, total do if select(5, GetFriendInfo(i)) then online = online + 1 end end
				self.text:SetText(format(modules.Friends.fmt, online, total))
			end
			if self.hovered then self:GetScript("OnEnter")(self) end
		end,
		OnUpdate = AltUpdate,
		OnClick = function(_,b) (b=="RightButton" and ToggleIgnorePanel or ToggleFriendsPanel)() end,
		OnEnter = function(self)
			ShowFriends()
			self.hovered = true
			local online, total = 0, GetNumFriends()
			local name, level, class, zone, connected, status, note, classc, levelc, zone_r, zone_g, zone_b, grouped
			for i = 0, total do if select(5, GetFriendInfo(i)) then online = online + 1 end end
			if online > 0 then
				GameTooltip:SetOwner(self, modules.Friends.tip_anchor, modules.Friends.tip_x, modules.Friends.tip_y)
				GameTooltip:ClearLines()
				GameTooltip:AddDoubleLine(FRIENDS_LIST, format("Online: %s/%s",online,total),tthead.r,tthead.g,tthead.b,tthead.r,tthead.g,tthead.b)
				GameTooltip:AddLine' '
				-- name, level, class, area, connected, status, note
				for i = 1, total do
					name, level, class, zone, connected, status, note = GetFriendInfo(i)
					if not connected then break end
					if GetRealZoneText() == zone then zone_r, zone_g, zone_b = 0.3, 1.0, 0.3 else zone_r, zone_g, zone_b = 0.65, 0.65, 0.65 end
					for k,v in pairs(LOCALIZED_CLASS_NAMES_MALE) do if class == v then class = k end end
					if GetLocale() ~= "enUS" then -- feminine class localization (unsure if it's really needed)
						for k,v in pairs(LOCALIZED_CLASS_NAMES_FEMALE) do if class == v then class = k end end
					end
					classc, levelc = (CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS)[class], GetQuestDifficultyColor(level)
					grouped = (UnitInParty(name) or UnitInRaid(name)) and (GetRealZoneText() == zone and " |cff7fff00*|r" or " |cffff7f00*|r") or ""
					GameTooltip:AddDoubleLine(format("|cff%02x%02x%02x%d|r %s%s%s",levelc.r*255,levelc.g*255,levelc.b*255,level,name,grouped," "..status),zone,classc.r,classc.g,classc.b,zone_r,zone_g,zone_b)
					if self.altdown and note then GameTooltip:AddLine("  "..note,ttsubh.r,ttsubh.g,ttsubh.b,1) end
				end
				GameTooltip:Show()
			else HideTT(self) end
		end
	})
end
------------------------------------------
--	Bags
if not modules.Bags.enabled then blank'Bags' else
	NewStat("Bags", {
		OnLoad = function(self)
			self:RegisterEvent'PLAYER_LOGIN'
			self:RegisterEvent'BAG_UPDATE'
		end,
		OnEvent = function(self)
			local free, total = 0, 0
			for i = 0, NUM_BAG_SLOTS do
				free, total = free + GetContainerNumFreeSlots(i), total + GetContainerNumSlots(i)
			end
			self.text:SetText(format(modules.Bags.fmt, free, total))
		end,
		OnClick = function() OpenAllBags() end
	})
end
------------------------------------------
--  Talents
if not modules.Talents.enabled then blank'Talents' else
	local timer = 0
	if modules.Talents.nospam then
		ChatFrame_AddMessageEventFilter("CHAT_MSG_SYSTEM", function(_,_,msg)
			if timer > GetTime() - 8 then
				if strfind(msg, gsub(ERR_LEARN_ABILITY_S,'%%s','(.*)')) then return true end
				if strfind(msg, gsub(ERR_LEARN_SPELL_S,'%%s','(.*)')) then return true end
				if strfind(msg, gsub(ERR_SPELL_UNLEARNED_S,'%%s','(.*)')) then return true end
			end
		end)
	end
	NewStat("Talents", { -- height = 11,
		OnLoad = function(self)
			self:RegisterEvent'PLAYER_LOGIN'
			self:RegisterEvent'PLAYER_TALENT_UPDATE'
			self:RegisterEvent'CHARACTER_POINTS_CHANGED'
			self:RegisterEvent'PLAYER_ENTERING_WORLD'
			self:RegisterEvent'PLAYER_LEAVING_WORLD'
			if modules.Talents.nospam then
				self:RegisterEvent'UNIT_SPELLCAST_START'
			end
		end,
		OnEvent = function(self, event, ...)
			if event == "PLAYER_ENTERING_WORLD" then
				self:RegisterEvent'PLAYER_TALENT_UPDATE'
			elseif event == "PLAYER_LEAVING_WORLD" then
				self:UnregisterEvent'PLAYER_TALENT_UPDATE'
			elseif event == "UNIT_SPELLCAST_START" then
				local unit, spell = ...
				if unit == "player" and (spell == GetSpellInfo(63645) or spell == GetSpellInfo(63644)) then timer = GetTime() end
			else
				if UnitLevel'player' < 10 then
					self.text:SetText'No Talents'
				elseif GetNumTalentTabs() == 3 then
					self.talents = {}
					self.unspent = UnitCharacterPoints'player'
					for i = 1, GetNumTalentGroups() do
						tinsert(self.talents, {})
						local tal, pts, icon, name = self.talents[i], -1
						for tree = 1, GetNumTalentTabs() do
							tinsert(tal, {GetTalentTabInfo(tree,nil,nil,i)})
							if tal[tree][3] ~= 0 and tal[tree][3] > pts then
								name, icon, pts = {tal[tree][1],tree}, tal[tree][2], tal[tree][3]
							end
						end
						if not name then name, icon = {"No Talents"}, "Interface\\Icons\\INV_Misc_QuestionMark" end
						tinsert(tal, name)
						if i == GetActiveTalentGroup() then
						
							self.text:SetText(gsub(gsub(gsub(modules.Talents.fmt,"%[(.-)%]", {
								name = name[1], shortname = gsub(name[1],".*",LPSTAT_SUBS),
								icon = format("|T%s:%d|t",icon,modules.Talents.iconsize),
								unspent = self.unspent > 0 and format("|cff55ff55+"..self.unspent) or ''
							}),"%[spec(.-)%]", function(spec)
								return format(spec == '' and "%d/%d/%d" or gsub(spec,'^ ',''),tal[1][3],tal[2][3],tal[3][3])
							end),' $',''))
							tinsert(tal, 1)
						end
					end
					if self.hovered then self:GetScript("OnEnter")(self) end
				end
			end
		end,
		OnUpdate = function(self)
			if GetNumTalentTabs() == 3 then
				self:SetScript("OnUpdate",nil)
				self:GetScript("OnEvent")(self)
			end
		end,
		OnEnter = function(self)
			self.hovered = true
			if UnitLevel'player' >= 10 then
				GameTooltip:SetOwner(self, modules.Talents.tip_anchor, modules.Talents.tip_x, modules.Talents.tip_y)
				GameTooltip:ClearLines()
				GameTooltip:AddDoubleLine(TALENTS,self.unspent > 0 and self.unspent..' Unspent' or '',tthead.r,tthead.g,tthead.b,tthead.r,tthead.g,tthead.b)
				GameTooltip:AddLine' '
				for i = 1, GetNumTalentGroups() do
					local tal = self.talents[i]
					local tree = tal[4][2]
					local name, icon, talents = tree and tal[tree][1] or "None", tree and tal[tree][2] or "Interface\\Icons\\INV_Misc_QuestionMark", format("%d/%d/%d",tal[1][3],tal[2][3],tal[3][3])
					if tal[5] then r,g,b = 0.3,1,0.3 else r,g,b = 0.5,0.5,0.5 end
					GameTooltip:AddDoubleLine(format("|T%s:%d|t %s %s",icon,t_icon,gsub(name,".*",LPSTAT_SUBS),talents), i==1 and PRIMARY or SECONDARY,1,1,1,r,g,b)
				end
				GameTooltip:Show()
			end
		end,
		OnLeave = function(self) self.hovered = false end,
		OnClick = function(_,b)
			if b == "RightButton" and GetNumTalentGroups() > 1 then
				SetActiveTalentGroup(3 - GetActiveTalentGroup())
			elseif b == "LeftButton" then
				ToggleTalentFrame()
			end
		end
	})
end
------------------------------------------
--	Character Stats
if not modules.Stats.enabled then blank'Stats' else
	local function tags(sub)
		local percent, string = true
		if sub == "ap" then
			local base, pos, neg = UnitAttackPower'player'
			string, percent = base + neg + pos
		elseif sub == "rangedap" then		
			local base, pos, neg = UnitRangedAttackPower'player'
			string, percent = base + neg + pos
		elseif sub == "armorpen" then
			string = GetArmorPenetration()
		elseif sub == "expertise" then
			string = GetExpertisePercent()
		elseif strmatch(sub,"hit$") then
			local var = getglobal("CR_HIT_"..(strupper(strmatch(sub,"(%w-)hit$")) or ""))
			string, percent = var and GetCombatRatingBonus(var) or format("[%s]",sub), var
		elseif strmatch(sub,"haste$") then
			local var = getglobal("CR_HASTE_"..(strupper(strmatch(sub,"(%w-)haste$")) or ""))
			string, percent = var and GetCombatRatingBonus(var) or format("[%s]",sub), var
		elseif sub == "meleecrit" then
			string = GetCritChance()
		elseif sub == "rangedcrit" then
			string = GetRangedCritChance()
		elseif sub == "spellcrit" then
			local rate = 0
			for i = 1, 7 do rate = max(rate, GetSpellCritChance(i)) end
			string = rate
		elseif sub == "spellpower" then
			local power = 0
			for i = 1, 7 do power = max(power, GetSpellBonusDamage(i)) end
			string, percent = power
		elseif sub == "healing" then
			string, percent = GetSpellBonusHealing()
		elseif sub == "spellpen" then
			string, percent = GetSpellPenetration()
		elseif sub == "dodge" then
			string = GetDodgeChance()
		elseif sub == "parry" then
			string = GetParryChance()
		elseif sub == "block" then
			string = GetBlockChance()
		elseif sub == "defense" then
			local base, add = UnitDefense'player'
			string, percent = base + add
		elseif sub == "avoidance" then
			string = GetDodgeChance() + GetParryChance() + GetBlockChance()
		elseif sub == "manaregen" then
			local _,class = UnitClass'player'
			local I5SR = true -- oo5sr/i5sr swapping NYI
			if class == "ROGUE" or class == "WARRIOR" or class == "DEATHKNIGHT" then
				string, percent = "??"
			else
				local base, cast = GetManaRegen()
				string, percent = floor((I5SR and cast or base) * 5)
			end
		elseif sub == "armor" then
			local _,eff = UnitArmor'player'
			string, percent = eff
		else
			string, percent = format("[%s]",sub)
		end
		if not percent then return string end
		return format("%.1f", string)
	end
	NewStat("Stats", {
		OnLoad = function(self)
			self:RegisterEvent'PLAYER_LOGIN'
		    self:RegisterEvent'UNIT_STATS'
		    self:RegisterEvent'UNIT_DAMAGE'
		    self:RegisterEvent'UNIT_RANGEDDAMAGE'
		    self:RegisterEvent'PLAYER_DAMAGE_DONE_MODS'
		    self:RegisterEvent'UNIT_ATTACK_SPEED'
		    self:RegisterEvent'UNIT_ATTACK_POWER'
		    self:RegisterEvent'UNIT_RANGED_ATTACK_POWER'
		end,
		OnEvent = function(self) self.fired = true end,
		OnUpdate = function(self, u)
			self.elapsed = self.elapsed + u
			if self.fired and self.elapsed > 2.5 then
				self.text:SetText(gsub(modules.Stats["spec"..GetActiveTalentGroup().."fmt"], "%[(%w-)%]", tags))
				self.elapsed, self.fired = 0, false
			end
		end
	})
end
------------------------------------------
--	Experience
if not modules.Experience.enabled then blank'Experience' else
	local logintime, playedtotal, playedlevel, playedmsg, gained, lastkill, lastquest = GetTime(), GetTime(), 0, 0, 0
	local function short(num,tt)
		if short or tt then
			num = tonumber(num)
			if num >= 1000000 then
				return gsub(format("%.1f%s", num/1000000, modules.Experience.million or 'm'),"%.0","")
			elseif num >= 1000 then
				return gsub(format("%.1f%s", num/1000, modules.Experience.thousand or 'k'),"%.0","")
			end
		end
		return floor(tonumber(num))
	end
	local function tags(sub,tt)
		local t = modules.Experience
		if sub == "level" then
			return UnitLevel'player'
		elseif sub == "curxp" then
			return short(UnitXP'player',tt)
		elseif sub == "remainingxp" then
			return short(UnitXPMax'player' - UnitXP'player',tt)
		elseif sub == "totalxp" then
			return short(UnitXPMax'player',tt)
		elseif sub == "cur%" then
			return floor(UnitXP'player' / UnitXPMax'player' * 100)
		elseif sub == "remaining%" then
			return 100 - floor(UnitXP'player' / UnitXPMax'player' * 100)
		elseif sub == "restxp" then
			return short(GetXPExhaustion() or 0,tt)
		elseif sub == "rest%" then
			return min(150, floor((GetXPExhaustion() or 0) / UnitXPMax'player' * 100))
		elseif sub == "sessiongained" then
			return short(gained,tt)
		elseif sub == "sessionrate" then
			return short(gained / (GetTime() - playedmsg) * 3600,tt)
		elseif sub == "levelrate" then
			return short(UnitXP'player' / (playedlevel + GetTime() - playedmsg) * 3600,tt)
		elseif sub == "sessionttl" then
			 return gained ~= 0 and fmttime((UnitXPMax'player' - UnitXP'player') / (gained / (GetTime() - playedmsg)),t) or "inf"
		elseif sub == "levelttl" then
			return UnitXP'player' ~= 0 and fmttime((UnitXPMax'player' - UnitXP'player') / (UnitXP'player' / (playedlevel + GetTime() - playedmsg)),t) or "inf"
		elseif sub == "questsleft" then
			return lastquest and ceil((UnitXPMax'player' - UnitXP'player') / lastquest) or "??"
		elseif sub == "killsleft" then
			return lastkill and ceil((UnitXPMax'player' - UnitXP'player') / lastkill) or "??"
		elseif sub == "playedtotal" then
			return fmttime(playedtotal + GetTime() - playedmsg,t)
		elseif sub == "playedlevel" then
			return fmttime(playedlevel + GetTime() - playedmsg,t)
		elseif sub == "playedsession" then
			return fmttime(GetTime() - playedmsg,t)
		else
			return format("[%s]",sub)
		end
	end
	NewStat("Experience", {
		text = {
			string = function()
				if UnitLevel'player' == 60 + 10 * GetAccountExpansionLevel() then
					return modules.Experience.maxlevel_fmt and gsub(modules.Experience.maxlevel_fmt,"%[([%w%%]-)%]",tags) or "Max Level"
				else
					return gsub(modules.Experience[(GetXPExhaustion() or 0) > 0 and "rested_fmt" or "fmt"],"%[([%w%%]-)%]",tags) or " "
				end				
			end
		},
		OnLoad = function(self)			
			self:RegisterEvent'TIME_PLAYED_MSG'
			self:RegisterEvent'PLAYER_LOGOUT'
			self:RegisterEvent'CHAT_MSG_COMBAT_XP_GAIN'
			self:RegisterEvent'PLAYER_LEVEL_UP'
			-- Filter first time played message.
			local ofunc = ChatFrame_DisplayTimePlayed
			function ChatFrame_DisplayTimePlayed() ChatFrame_DisplayTimePlayed = ofunc end
			RequestTimePlayed()
		end,
		OnEvent = function(self, event, ...)
			if event == "CHAT_MSG_COMBAT_XP_GAIN" then
				local msg = ...
				local mobxp = gsub(gsub(COMBATLOG_XPGAIN_FIRSTPERSON,"%%s","(.*)"),"%%d","(.*)")
				local questxp = gsub(gsub(COMBATLOG_XPGAIN_FIRSTPERSON_UNNAMED,"%%s","(.*)"),"%%d","(.*)")
				if msg:find(mobxp) then
					_, lastkill = strmatch(msg,mobxp)
					gained = gained + lastkill
				elseif msg:find(questxp) then
					lastquest = strmatch(msg,questxp)
					gained = gained + lastquest
				end
			elseif event == "PLAYER_LEVEL_UP" then
				playedlevel, playedmsg = 0, GetTime()
			elseif event == "TIME_PLAYED_MSG" then
				playedtotal, playedlevel = ...
				playedmsg = GetTime()
			end
			if event == "PLAYER_LOGOUT" or event == "TIME_PLAYED_MSG" then
				LPSTAT[char].Played = floor(playedtotal + GetTime() - playedmsg)
			end
		end,
		OnUpdate = AltUpdate,
		OnEnter = function(self)
			self.hovered = true
			GameTooltip:SetOwner(self, modules.Experience.tip_anchor, modules.Experience.tip_x, modules.Experience.tip_y)
			GameTooltip:ClearLines()
			if IsAltKeyDown() or UnitLevel'player' == 60 + 10 * GetAccountExpansionLevel() then
				GameTooltip:AddLine(TIME_PLAYED_MSG,tthead.r,tthead.g,tthead.b)
				GameTooltip:AddLine' '
				GameTooltip:AddDoubleLine("Played this session", tags("playedsession",1),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				GameTooltip:AddDoubleLine("Played this level", tags("playedlevel",1),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				GameTooltip:AddLine' '
				GameTooltip:AddLine('Account Played',ttsubh.r,ttsubh.g,ttsubh.b)
				
				local total = 0
				for char, conf in pairs(LPSTAT) do
					if conf.Played then
						local r,g,b,player = 1,1,1
						local name, realm = strmatch(char, '(.+) %- (.+)')
						if name == UnitName'player' and realm == GetRealmName() then
							conf.Played,player,r,g,b = floor(playedtotal + GetTime() - playedmsg),true,0.5,1,0.5
						end
						if conf.Played > 3600 or player then -- 1hr threshold displayed
							GameTooltip:AddDoubleLine(char, fmttime(conf.Played),r,g,b,1,1,1)
						end
						total = total + conf.Played
					end
				end				
				GameTooltip:AddDoubleLine(" ","------------------",1,1,1,0.5,0.5,0.5)
				GameTooltip:AddDoubleLine("Total",fmttime(total),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
			else
				GameTooltip:AddDoubleLine(COMBAT_XP_GAIN,"Level "..UnitLevel'player',tthead.r,tthead.g,tthead.b,tthead.r,tthead.g,tthead.b)
				GameTooltip:AddLine' '
				GameTooltip:AddDoubleLine("Current/Max XP",format("%s/%s (%s%%)",tags'curxp',tags'totalxp',tags'cur%'),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				GameTooltip:AddDoubleLine("Remaining XP",format("%s (%s%%)",tags'remainingxp',tags'remaining%'),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				if GetXPExhaustion() and GetXPExhaustion() ~= 0 then
					GameTooltip:AddDoubleLine("Rested XP",format("%s (%s%%)",tags'restxp',tags'rest%'),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				end
				GameTooltip:AddLine' '				
				GameTooltip:AddDoubleLine("Session XP rate",format("%s/hr (%s)",tags'sessionrate',tags'sessionttl'),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				GameTooltip:AddDoubleLine("Level XP rate",format("%s/hr (%s)",tags'levelrate',tags'levelttl'),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				GameTooltip:AddDoubleLine("Quests/Kills to "..UnitLevel'player'+1, format("Q:%s K:%s",tags'questsleft',tags'killsleft'),ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				GameTooltip:AddLine' '
				GameTooltip:AddDoubleLine("Played this session",tags'playedsession',ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				GameTooltip:AddDoubleLine("Played this level",tags'playedlevel',ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
				GameTooltip:AddDoubleLine("Played total",tags'playedtotal',ttsubh.r,ttsubh.g,ttsubh.b,1,1,1)
			end
			GameTooltip:Show()
		end
	})
end
------------------------------------------
-- Applying modules
lpanels:CreateLayout("LiteStats", layout)
lpanels:ApplyLayout(nil, "LiteStats")

NewStat, blank = nil
